var Request = function() {
    this.loaded = 0;
  };
  addEvents(Request, 'load', 'progress', 'error');

  Request.prototype.progress = function(loaded, total, lengthComputable) {

	

  this.loaded = loaded;
    this.total = total;
    this.lengthComputable = lengthComputable;
    this.dispatchProgress({
      loaded: loaded,
      total: total,
      lengthComputable: lengthComputable
    });
  };

  Request.prototype.load = function(result) {
    this.result = result;
    this.isDone = true;
    this.dispatchLoad(result);
  };

  Request.prototype.error = function(error) {
    this.result = error;
    this.isError = true;
    this.isDone = true;
    this.dispatchError(error);
  };

  var CompositeRequest = function() {
    this.requests = [];
    this.isDone = true;
    this.update = this.update.bind(this);
    this.error = this.error.bind(this);
  };
  inherits(CompositeRequest, Request);

  CompositeRequest.prototype.add = function(request) {
    if (request instanceof CompositeRequest) {
      for (var i = 0; i < request.requests.length; i++) {
        this.add(request.requests[i]);
      }
    } else {
      this.requests.push(request);
      request.addEventListener('progress', this.update);
      request.addEventListener('load', this.update);
      request.addEventListener('error', this.error);
      this.update();
    }
  };

  CompositeRequest.prototype.update = function() {
	  
	  
	  
    if (this.isError) return;
    var requests = this.requests;
    var i = requests.length;
    var total = 0;
    var loaded = 0;
    var lengthComputable = true;
    var uncomputable = 0;
    var done = 0;
    while (i--) {
      var r = requests[i];
      loaded += r.loaded;
      if (r.isDone) {
        total += r.loaded;
        done += 1;
      } else if (r.lengthComputable) {
        total += r.total;
      } else {
        lengthComputable = false;
        uncomputable += 1;
      }
    }
    if (!lengthComputable && uncomputable !== requests.length) {
      var each = total / (requests.length - uncomputable) * uncomputable;
      i = requests.length;
      total = 0;
      loaded = 0;
      lengthComputable = true;
      while (i--) {
        var r = requests[i];
        if (r.lengthComputable) {
          loaded += r.loaded;
          total += r.total;
        } else {
          total += each;
          if (r.isDone) loaded += each;
        }
      }
    }
    this.progress(loaded, total, lengthComputable);
    this.doneCount = done;
    this.isDone = done === requests.length;
    if (this.isDone && !this.defer) {
      this.load(this.getResult());
    }
  };

  CompositeRequest.prototype.getResult = function() {
    throw new Error('Users must implement getResult()');
  };

  var wavFiles = {AcousticGuitar_F3:'instruments/AcousticGuitar_F3_22k.wav',AcousticPiano_As3:'instruments/AcousticPiano(5)_A%233_22k.wav',AcousticPiano_C4:'instruments/AcousticPiano(5)_C4_22k.wav',AcousticPiano_G4:'instruments/AcousticPiano(5)_G4_22k.wav',AcousticPiano_F5:'instruments/AcousticPiano(5)_F5_22k.wav',AcousticPiano_C6:'instruments/AcousticPiano(5)_C6_22k.wav',AcousticPiano_Ds6:'instruments/AcousticPiano(5)_D%236_22k.wav',AcousticPiano_D7:'instruments/AcousticPiano(5)_D7_22k.wav',AltoSax_A3:'instruments/AltoSax_A3_22K.wav',AltoSax_C6:'instruments/AltoSax(3)_C6_22k.wav',Bassoon_C3:'instruments/Bassoon_C3_22k.wav',BassTrombone_A2_2:'instruments/BassTrombone_A2(2)_22k.wav',BassTrombone_A2_3:'instruments/BassTrombone_A2(3)_22k.wav',Cello_C2:'instruments/Cello(3b)_C2_22k.wav',Cello_As2:'instruments/Cello(3)_A%232_22k.wav',Choir_F3:'instruments/Choir(4)_F3_22k.wav',Choir_F4:'instruments/Choir(4)_F4_22k.wav',Choir_F5:'instruments/Choir(4)_F5_22k.wav',Clarinet_C4:'instruments/Clarinet_C4_22k.wav',ElectricBass_G1:'instruments/ElectricBass(2)_G1_22k.wav',ElectricGuitar_F3:'instruments/ElectricGuitar(2)_F3(1)_22k.wav',ElectricPiano_C2:'instruments/ElectricPiano_C2_22k.wav',ElectricPiano_C4:'instruments/ElectricPiano_C4_22k.wav',EnglishHorn_D4:'instruments/EnglishHorn(1)_D4_22k.wav',EnglishHorn_F3:'instruments/EnglishHorn(1)_F3_22k.wav',Flute_B5_1:'instruments/Flute(3)_B5(1)_22k.wav',Flute_B5_2:'instruments/Flute(3)_B5(2)_22k.wav',Marimba_C4:'instruments/Marimba_C4_22k.wav',MusicBox_C4:'instruments/MusicBox_C4_22k.wav',Organ_G2:'instruments/Organ(2)_G2_22k.wav',Pizz_A3:'instruments/Pizz(2)_A3_22k.wav',Pizz_E4:'instruments/Pizz(2)_E4_22k.wav',Pizz_G2:'instruments/Pizz(2)_G2_22k.wav',SteelDrum_D5:'instruments/SteelDrum_D5_22k.wav',SynthLead_C4:'instruments/SynthLead(6)_C4_22k.wav',SynthLead_C6:'instruments/SynthLead(6)_C6_22k.wav',SynthPad_A3:'instruments/SynthPad(2)_A3_22k.wav',SynthPad_C6:'instruments/SynthPad(2)_C6_22k.wav',TenorSax_C3:'instruments/TenorSax(1)_C3_22k.wav',Trombone_B3:'instruments/Trombone_B3_22k.wav',Trumpet_E5:'instruments/Trumpet_E5_22k.wav',Vibraphone_C3:'instruments/Vibraphone_C3_22k.wav',Violin_D4:'instruments/Violin(2)_D4_22K.wav',Violin_A4:'instruments/Violin(3)_A4_22k.wav',Violin_E5:'instruments/Violin(3b)_E5_22k.wav',WoodenFlute_C5:'instruments/WoodenFlute_C5_22k.wav',BassDrum:'drums/BassDrum(1b)_22k.wav',Bongo:'drums/Bongo_22k.wav',Cabasa:'drums/Cabasa(1)_22k.wav',Clap:'drums/Clap(1)_22k.wav',Claves:'drums/Claves(1)_22k.wav',Conga:'drums/Conga(1)_22k.wav',Cowbell:'drums/Cowbell(3)_22k.wav',Crash:'drums/Crash(2)_22k.wav',Cuica:'drums/Cuica(2)_22k.wav',GuiroLong:'drums/GuiroLong(1)_22k.wav',GuiroShort:'drums/GuiroShort(1)_22k.wav',HiHatClosed:'drums/HiHatClosed(1)_22k.wav',HiHatOpen:'drums/HiHatOpen(2)_22k.wav',HiHatPedal:'drums/HiHatPedal(1)_22k.wav',Maracas:'drums/Maracas(1)_22k.wav',SideStick:'drums/SideStick(1)_22k.wav',SnareDrum:'drums/SnareDrum(1)_22k.wav',Tambourine:'drums/Tambourine(3)_22k.wav',Tom:'drums/Tom(1)_22k.wav',Triangle:'drums/Triangle(1)_22k.wav',Vibraslap:'drums/Vibraslap(1)_22k.wav',WoodBlock:'drums/WoodBlock(1)_22k.wav'};

  function readBytes(e, t, r) {
    for (var n = 0, a = 0; a < t; a++)
        n += r[e + a] << 8 * a;
    return n
  }

  function readADPCM(e) {
    var t = readBytes(32, 2, e)
      , r = t - 4
      , n = readBytes(24, 4, e)
      , a = 1 != readBytes(20, 2, e) ? 38 + readBytes(36, 2, e) : 36
      , o = readBytes((a += 8 + readBytes(a + 4, 4, e)) + 4, 4, e) / t;
    a += 8;
    for (var i, s, u, d, l = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8], c = [7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767], f = 8, m = 16, _ = 0, v = 0, h = 0, g = (4 * r + 2) * o, O = new ArrayBuffer(g + 32), p = new Uint8Array(O,32,g), w = 0; w < o; w++) {
        32767 < (_ = readBytes((i = h) + a, 2, e)) && (_ -= 65536),
        f = Math.max(0, Math.min(readBytes(h + a + 2, 1, e), 88)),
        h += 4,
        (b = Math.round(_)) < 0 && (b += 65536),
        p[v++] = b % 256,
        p[v++] = Math.floor(b / 256);
        for (var I = 0; I < r; I++) {
            for (s = e[h + a].toString(2); s.length < 8; )
                s = "0" + s;
            for (var A = 0; A < 2; A++) {
                var b;
                u = parseInt(s.substr(4 * A, 4), 2),
                d = (m = c[f]) >> 3,
                1 & (u &= 15) && (d += m >> 2),
                2 & u && (d += m >> 1),
                4 & u && (d += m),
                8 & u && (d = 0 - d),
                _ = Math.max(Math.min(32767, _ + d), -32768),
                (b = Math.round(_)) < 0 && (b += 65536),
                p[v++] = b % 256,
                p[v++] = Math.floor(b / 256),
                f = Math.max(0, Math.min(f + l[u], 88))
            }
            h += 1
        }
        h = i + t
    }
    return encodeAudio16bit(p, n, O)
}

function encodeAudio16bit(e, t, r) {
  var n = "RIFF<##>WAVEfmt \0\0\0\0\0<##><##>\0\0data<##>";
  function a(e) {
      for (var t = "", r = 0; r < 4; ++r)
          t += String.fromCharCode(e % 256),
          e = Math.floor(e / 256);
      n = n.replace("<##>", t)
  }
  var o = e.length / 2;
  a(36 + 2 * o),
  a(t),
  a(2 * t),
  a(2 * o);
  for (var i = new Uint8Array(r,0,40), s = 0; s < n.length; s++)
      i[s] = n.charCodeAt(s);
  return r.slice(0)
}


  var IO = {};

  //IO.PROJECT_URL = 'https://projects.scratch.mit.edu/internalapi/project/';
  IO.PROJECT_URL = 'http://localhost/subProg/projects/';
  //IO.PROJECT_URL = 'https://wx.wit-learn.com/subProg/projects/';
  //IO.PROJECT_URL = 'http://www.kwfuture.cn/subApps/resources/projects/';

  
  
  //IO.ASSET_URL = 'https://cdn.assets.scratch.mit.edu/internalapi/asset/';
  IO.ASSET_URL = 'http://localhost/subProg/asset/';
  //IO.ASSET_URL = 'https://wx.wit-learn.com/subProg/asset/';
  //IO.ASSET_URL = 'http://www.kwfuture.cn/subApps/resources/asset/';
  

  //IO.SOUNDBANK_URL = window.location + 'soundbank/';
  IO.SOUNDBANK_URL = 'http://localhost/subProg/Sulfurous-master/soundbank/';
  //IO.SOUNDBANK_URL = 'https://wx.wit-learn.com/subProg/Sulfurous-master/soundbank/';
  //IO.SOUNDBANK_URL = 'http://www.kwfuture.cn/subApps/Sulfurous-master/soundbank/';

   IO.FONTS = {
    '': 'Helvetica',
    Scratch: 'Scratch',	  
    Donegal: 'Donegal One',
    Gloria: 'Gloria Hallelujah',
    Marker: 'Permanent Marker',
    Mystery: 'Mystery Quest'
  };

  IO.LINE_HEIGHTS = {
    'Helvetica': 1.13,
    'Scratch': 1.0,
    'Donegal One': 1.25,
    'Gloria Hallelujah': 1.97,
    'Permanent Marker': 1.43,
    'Mystery Quest': 1.37
  };
  
  IO.ADPCM_STEPS = [7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767];
  IO.ADPCM_INDEX = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8];

  IO.init = function(request) {
    IO.projectRequest = request;
    IO.zip = null;
  };

  IO.parseJSONish = function(json) {
    if (!/^\s*\{/.test(json)) throw new SyntaxError('Bad JSON');
    try {
      return JSON.parse(json);
    } catch (e) {}
    if (/[^,:{}\[\]0-9\.\-+EINaefilnr-uy \n\r\t]/.test(json.replace(/"(\\.|[^"\\])*"/g, ''))) {
      throw new SyntaxError('Bad JSON');
    }
    return (1, eval)('(' + json + ')');
  };


  IO.load = function(url, callback, self, type) {
  
   var request = new Request;
    var xhr = new XMLHttpRequest;
    xhr.open('GET', url, true);
	
	 
    xhr.onprogress = function(e) {
		
		
      request.progress(e.loaded, e.total, e.lengthComputable);
    };
    xhr.onload = function() {
      if (xhr.status === 200) {
        request.load(xhr.response);
      } else {
		  
        request.error(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));
      }
    };
    xhr.onerror = function() {
     request.error(new Error('XHR Error'));
    };
    xhr.responseType = type || '';
    setTimeout(xhr.send.bind(xhr));

    if (callback) request.onLoad(callback.bind(self));
	
    return request;
	
  };

  IO.loadImage = function(url, callback, self) {
    var request = new Request;
    var image = new Image;
	  var bForcedBlank = false;
    image.crossOrigin = 'anonymous';
    image.src = url;
    image.onload = function() {
      request.load(image);
    };
    image.onerror = function() {
     // request.error(new Error('Failed to load image: ' + url));
	  console.log('Failed to load image (forcing blank): ' + url);
      bForcedBlank = true;
      image.src = "";
  
    };
    if (callback) request.onLoad(callback.bind(self));
     return (bForcedBlank) ? request : request;
  };
		
  IO.loadScratchr2Project = function(id, callback, self) {
    var request = new CompositeRequest;
    IO.init(request);
	
	projectID = id;
	
    request.defer = true;
    //var url = IO.PROJECT_URL + id + '/get/';
    var url = IO.PROJECT_URL + id;
    
	console.log(url);
	
	request.add(IO.load(url).onLoad(function(contents) {
		
		
      try {
        var json = IO.parseJSONish(contents);
      } catch (e) {
        request.add(IO.load(url, null, null, 'arraybuffer').onLoad(function(ab) {
          var request2 = new Request;
          request.add(request2);
          request.add(IO.loadSB2Project(ab, function(stage) {
			
            request.getResult = function() {
              return stage;
            };
            request2.load();
          }));
          request.defer = false;
        }));
        return;
      }
      try {
        IO.loadProject(json);
        if (callback) request.onLoad(callback.bind(self));
        if (request.isDone) {
          request.load(new Stage().fromJSON(json));
        } else {
          request.defer = false;
          request.getResult = function() {
            return new Stage().fromJSON(json);
          };
        }
      } catch (e) {
        request.error(e);
      }
    }));

    return request;
  };

  IO.loadScratchr2ProjectTitle = function(id, callback, self) {
    var request = new CompositeRequest;    
    
    request.defer = true;
    //'https://scratch.mit.edu/projects/' + id + '/'
    //https://wx.wit-learn.com/subProg/projects/
    //http://www.kwfuture.cn/subApps
    request.add(P.IO.load('http://localhost/subProg/projects/' + id).onLoad(function(data) {
      var m = /<title>\s*(.+?)(\s+on\s+Scratch)?\s*<\/title>/.exec(data);
      if (callback) request.onLoad(callback.bind(self));
      if (m) {
        var d = document.createElement('div');
        d.innerHTML = m[1];
        request.load(d.innerText);
	
      } else {
        request.error(new Error('No title'));
      }
    }));

    return request;
  };

  IO.loadJSONProject = function(json, callback, self) {
    var request = new CompositeRequest;
    IO.init(request);

    try {
      IO.loadProject(json);
      if (callback) request.onLoad(callback.bind(self));
      if (request.isDone) {
        request.load(new Stage().fromJSON(json));
      } else {
        request.defer = false;
        request.getResult = function() {
          return new Stage().fromJSON(json);
        };
      }
    } catch (e) {
      request.error(e);
    }

    return request;
  };

  IO.loadSB2Project = function(ab, callback, self) {
    var request = new CompositeRequest;
    IO.init(request);

    try {
      IO.zip = new JSZip(ab);
      var json = IO.parseJSONish(IO.zip.file('project.json').asText());

      IO.loadProject(json);
      if (callback) request.onLoad(callback.bind(self));
      if (request.isDone) {
        request.load(new Stage().fromJSON(json));
      } else {
        request.defer = false;
        request.getResult = function() {
          return new Stage().fromJSON(json);
        };
      }
    } catch (e) {
      request.error(e);
    }

    return request;
  };

  IO.loadSB2File = function(f, callback, self) {
    var cr = new CompositeRequest;
    cr.defer = true;
    var request = new Request;
    cr.add(request);
    var reader = new FileReader;
    reader.onloadend = function() {
      cr.defer = true;
      cr.add(IO.loadSB2Project(reader.result, function(result) {
        cr.defer = false;
        cr.getResult = function() {
          return result;
        };
        cr.update();
      }));
      request.load();
    };
    reader.onprogress = function(e) {
      request.progress(e.loaded, e.total, e.lengthComputable);
    };
    reader.readAsArrayBuffer(f);
    if (callback) cr.onLoad(callback.bind(self));
    return cr;
  };

  IO.loadProject = function(data) {
    IO.loadWavs();
    IO.loadArray(data.children, IO.loadObject);
    IO.loadBase(data);
  };

  IO.wavBuffers = Object.create(null);
  IO.loadWavs = function() {
    if (!audioContext) return;

    for (var name in wavFiles) {
      if (IO.wavBuffers[name]) {
        if (IO.wavBuffers[name] instanceof Request) {
          IO.projectRequest.add(IO.wavBuffers[name]);
        }
      } else {
        IO.projectRequest.add(IO.wavBuffers[name] = IO.loadWavBuffer(name));
      }
    }
  };
  
  IO.arrayBufferToBase64 = function(buffer){
    var bytes = new Uint8Array(buffer);
    var len = buffer.byteLength;
    var base64 = '';
    for(var i = 0; i < len; i++){
      base64 += String.fromCharCode(bytes[i]);
    }
    return btoa(base64);
  }
  
  IO.base64ToArrayBuffer = function(base64){
    var binaryString = atob(base64);
    var len = binaryString.length;
    var bytes = new Uint8Array(len);
    for(var i = 0; i < len; i++){
      bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
  }
  
  //IO.base64output = '';
  
  IO.loadWavBuffer = function(name) {
    var request = new Request;
	

		
    IO.load(IO.SOUNDBANK_URL + wavFiles[name], function(ab) {
      
      //Code for exporting soundbank to text file.
      /*
      IO.base64output = IO.base64output + 'A.' + name + ' = \'' + IO.arrayBufferToBase64(ab) + '\'\n';
      var el = document.createElement("a");
      el.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(IO.base64output));
      el.appendChild(document.createTextNode('wav files'));
      document.body.appendChild(el);
      console.log(name);
      */     
      
      IO.decodeAudio(ab, function(buffer) {
        IO.wavBuffers[name] = buffer;
        request.load();
      });
    }, null, 'arraybuffer').onError(function(err) {
      request.error(err);
    });
    return request;
  };

  
  IO.decodeAudio = function(e, r) {
    if (audioContext) {
      var t = !1
        , n = new Uint8Array(e);
      17 == readBytes(20, 2, n) && (t = readADPCM(n)),
      t ? IO.decodeADPCMAudio(e, function(e, t) {
          r(t)
      }, function(e) {
          r(null)
      }) : audioContext.decodeAudioData(e, function(e) {
          r(e)
      }, function(e) {
          r(null)
      })
  } else
      setTimeout(r)
  };

  IO.decodeADPCMAudio = function(e, t) {
    var r = new DataView(e);
    if (1380533830 !== r.getUint32(0) || 1463899717 !== r.getUint32(8))
        return t(new Error("Unrecognized audio format"));
    for (var n = {}, a = 12, o = r.byteLength - 8; a < o; )
        n[String.fromCharCode(r.getUint8(a), r.getUint8(a + 1), r.getUint8(a + 2), r.getUint8(a + 3))] = a,
        a += 8 + r.getUint32(a + 4, !0);
    var i = r.getUint16(20, !0)
      , s = (r.getUint16(22, !0),
    r.getUint32(24, !0));
    r.getUint32(28, !0),
    r.getUint16(32, !0),
    r.getUint16(34, !0);
    if (17 === i) {
        var u, d = (r.getUint16(38, !0) - 1) / 2 + 4, l = r.getUint32(n.fact + 8, !0);
        try {
            u = audioContext.createBuffer(1, l, s)
        } catch (e) {
            u = audioContext.createBuffer(1, l, 44100)
        }
        var c, f, m, _, v = u.getChannelData(0), h = 0, g = -1, O = n.data + 8;
        a = O;
        for (var p = 0; ; )
            if ((a - O) % d == 0 && g < 0) {
                if (a >= r.byteLength)
                    break;
                c = r.getInt16(a, !0),
                a += 2,
                h = r.getUint8(a),
                a += 1,
                a++,
                88 < h && (h = 88),
                v[p++] = c / 32767
            } else {
                if (g < 0) {
                    if (a >= r.byteLength)
                        break;
                    g = r.getUint8(a),
                    a += 1,
                    m = 15 & g
                } else
                    m = g >> 4 & 15,
                    g = -1;
                f = IO.ADPCM_STEPS[h],
                _ = 0,
                4 & m && (_ += f),
                2 & m && (_ += f >> 1),
                1 & m && (_ += f >> 2),
                _ += f >> 3,
                88 < (h += IO.ADPCM_INDEX[m]) && (h = 88),
                h < 0 && (h = 0),
                32767 < (c += 8 & m ? -_ : _) && (c = 32767),
                c < -32768 && (c = -32768),
                v[p++] = c / 32768
            }
        return t(null, u)
    }
    t(new Error("Unrecognized WAV format " + i))
  };
  
  IO.loadBase = function(data) {
    data.scripts = data.scripts || [];
    data.costumes = IO.loadArray(data.costumes, IO.loadCostume);
    data.sounds = IO.loadArray(data.sounds, IO.loadSound);
    data.variables = data.variables || [];
    data.lists = data.lists || [];
	
	console.log(data.lists);		
		 //pf temp (dirty) hack for ASCII hack lists...		
	    if (data && data.lists && data.lists.length) {		
	        for (var ha = data.lists.length; ha--;)		
		{		
		    if (data.lists[ha].listName == "ASCII" && data.lists[ha].contents.length != 133) { // 2nd part ugh (skips GB ROM) !!!		
		        ASCII = true;		
			console.log("ASCII hack detected.");		
		    }		
		}		
	    } 
	
  };

	//process an array of several inputs (such as costumes, sounds, ...)
  IO.loadArray = function(data, process) {
    if (!data) return [];
    for (var i = 0; i < data.length; i++) {
      process(data[i]);
    }
    return data;
  };

  IO.loadObject = function(data) {
    if (!data.cmd && !data.listName) {
      IO.loadBase(data);
    }
  };

  IO.loadCostume = function(data) {
    IO.loadMD5(data.baseLayerMD5, data.baseLayerID, function(asset) {
      data.$image = asset;
    }, false);
    if (data.textLayerMD5) {
      IO.loadMD5(data.textLayerMD5, data.textLayerID, function(asset) {
        data.$text = asset;
      }, false);
    }
  };

  IO.loadSound = function(data) {
    IO.loadMD5(data.md5, data.soundID, function(asset) {
      data.$buffer = asset;
    }, true);
  };

  IO.fixSVG = function(svg, element) {
	  
	  
	  
    if (element.nodeType !== 1) return element;
    if (element.nodeName.slice(0, 4).toLowerCase() === 'svg:') {
     /*
      var newElement = document.createElementNS('https://www.w3.org/2000/svg', element.localName);
      var attributes = element.attributes;
      var newAttributes = newElement.attributes;
      for (var i = attributes.length; i--;) {
        newAttributes.setNamedItemNS(attributes[i].cloneNode());
      }
      while (element.firstChild) {
        newElement.appendChild(element.firstChild);
      }
      element = newElement;
     */
    }
    
    //Embed fonts in svg:
    if (element.nodeName === 'svg') {
      var defs = document.createElement('defs');
      element.appendChild(defs);
      
      var style = document.createElement('style');
      defs.appendChild(style);
      
      var embedText = '';
      
      
      if(element.querySelector('[font-family="Scratch"]')){
        embedText += '@font-face{\n';
        embedText += 'font-family: Scratch;\nsrc: url(\"data:application/x-font-ttf;base64,';
        embedText += F.Scratch;
        embedText += '\");\n';
        embedText += '}\n';
      }
      
      if(element.querySelector('[font-family="Donegal"]')){
        embedText += '@font-face{\n';
        embedText += 'font-family: Donegal One;\nsrc: url(\"data:application/x-font-ttf;base64,';
        embedText += F.Donegal;
        embedText += '\");\n';
        embedText += '}\n';      
      }
      
      if(element.querySelector('[font-family="Gloria"]')){
        embedText += '@font-face{\n';
        embedText += 'font-family: Gloria Hallelujah;\nsrc: url(\"data:application/x-font-ttf;base64,';
        embedText += F.Gloria;
        embedText += '\");\n';
        embedText += '}\n';      
      }
      
      if(element.querySelector('[font-family="Marker"]')){
        embedText += '@font-face{\n';
        embedText += 'font-family: Permanent Marker;\nsrc: url(\"data:application/x-font-ttf;base64,';
        embedText += F.Marker;
        embedText += '\");\n';
        embedText += '}\n';      
      }
      
      if(element.querySelector('[font-family="Mystery"]')){
        embedText += '@font-face{\n';
        embedText += 'font-family: Mystery Quest;\nsrc: url(\"data:application/x-font-ttf;base64,';
        embedText += F.Mystery;
        embedText += '\");\n';
        embedText += '}\n';            
      }
      
      var info = document.createTextNode(embedText);
      style.appendChild(info);
    }
    
    if (element.nodeName === 'text') {
      
      var font = element.getAttribute('font-family') || '';
      
      font = IO.FONTS[font] || font;
      if (font) {
        element.setAttribute('font-family', font);
        if (font === 'Helvetica') element.style.fontWeight = 'bold';
      }
      var size = +element.getAttribute('font-size');
      if (!size) {
        element.setAttribute('font-size', size = 18);
      }
			
			
			// Set default fill for svgs that the Scratch exporter forgets...
			if(element.getAttribute('fill') === 'none')
				element.setAttribute('fill', '#7F7F7F');
      
      
      //TODO: Find out what actual values have to be put here.
      //element.setAttribute('x', 0);
      //element.setAttribute('y', size*IO.LINE_HEIGHTS[font]);
      var bb = element ? element.getBBox() : null;
      var x = 4 - .6 * element.transform.baseVal.consolidate().matrix.a;
      var y = (element.getAttribute('y') - bb.y) * 1.1;
      element.setAttribute('x', x);
      element.setAttribute('y', y);
      
      
      var lines = element.textContent.split('\n');
      if (lines.length > 1) {
        element.textContent = lines[0];
        var lineHeight = IO.LINE_HEIGHTS[font] || 1;
        for (var i = 1, l = lines.length; i < l; i++) {
          var tspan = document.createElementNS("http://www.w3.org/2000/svg", 'tspan');
          tspan.textContent = lines[i];
          tspan.setAttribute('x', x);
          tspan.setAttribute('y', y + size * i * lineHeight);
          element.appendChild(tspan);
        }
        
      }
      
      
      
    } else if ((element.hasAttribute('x') || element.hasAttribute('y')) && element.hasAttribute('transform')) {
      element.setAttribute('x', 0);
      element.setAttribute('y', 0);
    }
    
    if (element.nodeName === 'linearGradient'){
      element.setAttribute('id', element.getAttribute('id') + svg.getAttribute('id'));
        element.setAttribute('gradientUnits', 'objectBoundingBox');
        //I really don't know what kind of algorithm scratch is following here, so this is just guesswork.
        var x1 = Number(element.getAttribute('x1'));
        var x2 = Number(element.getAttribute('x2'));
        var y1 = Number(element.getAttribute('y1'));
        var y2 = Number(element.getAttribute('y2'));
        
        if(x1 === x2){
          x1 = 0;
          x2 = 0;
        }
        else if(x1 < x2){
          x1 = 0;
          x2 = 1;
        }
        else{
          x1 = 1;
          x2 = 0;
        }
        if(y1 === y2){
          y1 = 0;
          y2 = 0;
        }
        else if(y1 < y2){
          y1 = 0;
          y2 = 1;
        }
        else{
          y1 = 1;
          y2 = 0;
        }
        
        element.setAttribute('x1', x1);
        element.setAttribute('x2', x2);
        element.setAttribute('y1', y1);
        element.setAttribute('y2', y2);
 
    }
	
		if(element.nodeName === 'radialGradient'){
			element.setAttribute('id', element.getAttribute('id') + svg.getAttribute('id'));
			element.setAttribute('gradientUnits', 'objectBoundingBox');
		}
    
    if (element.getAttribute('fill') ? element.getAttribute('fill').indexOf("url") > -1 : false){
      element.setAttribute('fill', element.getAttribute('fill').replace(/.$/, svg.getAttribute('id')));
    }
    
    if (element.getAttribute('stroke') ? element.getAttribute('stroke').indexOf("url") > -1 : false){
      element.setAttribute('stroke', element.getAttribute('stroke').replace(/.$/, svg.getAttribute('id')));
    }
    
    [].forEach.call(element.childNodes, function(child){
	  var newChild = IO.fixSVG(svg, child);
      if (newChild !== child) {
        element.replaceChild(newChild, child);
      }
	});
	return element;
  };

  IO.loadMD5 = function(md5, id, callback, isAudio) {
		if (IO.zip) {
      var f = isAudio ? IO.zip.file(id + '.wav') : IO.zip.file(id + '.gif') || IO.zip.file(id + '.png') || IO.zip.file(id + '.jpg') || IO.zip.file(id + '.svg');
      md5 = f.name;
    }
		//get file extension
    var ext = md5.split('.').pop();
		//special handling for svg
    if (ext === 'svg') {
      var cb = function(source) {
        var div = document.createElement('div');
        //div.innerHTML = source;
        //var svg = div.getElementsByTagName('svg')[0];
        //div.innerHTML = source.replace(/(<\/?)svg:/g, '$1');
        //var svg = div.firstElementChild;		
				
        var parser = new DOMParser();
        var doc = parser.parseFromString(source, 'image/svg+xml');
        var svg = doc.documentElement;
        doc = parser.parseFromString('<body>' + source, 'text/html');
        svg = doc.querySelector('svg');
        
        svg.id = 'svg' + md5.split('.')[0];
        if(svg.getAttribute('width') === '0' || svg.getAttribute('height') === '0'){
          svg = document.createElementNS('https://www.w3.org/2000/svg', svg.localName);
        }
        else{
          document.body.appendChild(svg);
          svg = IO.fixSVG(svg, svg);
        }
		
		//Some svg tags are completely emty for some reason, so we simply ignore these
		if(!svg.style) return;		
		//When viewBox doesn't exist yet, some browsers don't automatically create an object,
		//so we need to do that manually. (SVGRect doesn't seem to have a constructor either)
        var viewBox = svg.viewBox && svg.viewBox.baseVal ? svg.viewBox.baseVal : {width: 0, height: 0, x: 0, y: 0};
		
        if (svg.querySelector("path") || svg.querySelector("image")) {
        var bb = svg.getBBox();
        viewBox.width  = svg.width.baseVal.value = Math.ceil(bb.x + bb.width + 1);
        viewBox.height = svg.height.baseVal.value = Math.ceil(bb.y + bb.height + 1);				
        viewBox.x = 0;
        viewBox.y = 0;
      }
				//get the viewbox of the svg
        if (viewBox && (viewBox.x || viewBox.y)) {
          //svg.width.baseVal.value = viewBox.width - viewBox.x;
          //svg.height.baseVal.value = viewBox.height - viewBox.y;
          //viewBox.x = 0;
          //viewBox.y = 0;
          //viewBox.width = 0;
          //viewBox.height = 0;
          var bb = svg.getBBox();
          viewBox.width  = svg.width.baseVal.value = Math.ceil(bb.x + bb.width + 1);
          viewBox.height = svg.height.baseVal.value = Math.ceil(bb.y + bb.height + 1);	
          viewBox.x = 0;
          viewBox.y = 0;
        }

        //IO.fixSVG(svg, svg);
        //while (div.firstChild) div.removeChild(div.lastChild);
        //div.appendChild(svg);
        //svg.style.visibility = 'visible';
        //svg.style.cssText = '';

        svg.style['image-rendering'] = '-moz-crisp-edges';
        svg.style['image-rendering'] = 'pixelated';
		
        //svg.style.overflow = 'visible';
        //svg.style.width = '100%';
        
        var request = new Request;
        var image = new Image;

				//serialize the svg code to a single compact string
        var newSource = (new XMLSerializer()).serializeToString(svg)
				//convert the svg to a base-64 string
        image.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(newSource))); 
        
        //svg.style.display = 'none';
        document.body.removeChild(svg);
        
        image.onload = function() {
          if (callback) callback(image);
          request.load();
        };
        image.onerror = function(e) {
          //console.error(e, image);
          console.log(image.src);
          console.error(md5, image.src);
          request.error(new Error());
        };
        IO.projectRequest.add(request);		
      };
      if (IO.zip) {
        cb(f.asText());
      } else {
        IO.projectRequest.add(IO.load(IO.ASSET_URL + md5 + '/get/', cb));
      }
    } else if (ext === 'wav') {
      var request = new Request;
      var cb = function(ab) {
        IO.decodeAudio(ab, function(buffer) {
          callback(buffer);
          request.load(buffer);
        });
      }
      IO.projectRequest.add(request);
      if (IO.zip) {
        var audio = new Audio;
        var ab = f.asArrayBuffer();
        cb(ab);
      } else {
        IO.projectRequest.add(IO.load(IO.ASSET_URL + md5 + '/get/', cb, null, 'arraybuffer'));
      }
    } else {
      if (IO.zip) {
        var request = new Request;
        var image = new Image;
        image.onload = function() {
          if (callback) callback(image);
          request.load();
        };
        image.src = 'data:image/' + (ext === 'jpg' ? 'jpeg' : ext) + ';base64,' + btoa(f.asBinary());
        IO.projectRequest.add(request);
      } else {
        IO.projectRequest.add(
          IO.loadImage(IO.ASSET_URL + md5 + '/get/', function(result) {
            callback(result);
          }));
      }
    }
  };